package com.xqboss.apps.service.game;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.constant.RedisKey;
import com.xqboss.apps.domain.game.GameMaterialStrikeRecord;
import com.xqboss.apps.dto.game.GameMaterialStrikePlayDto;
import com.xqboss.apps.dto.game.GameMaterialStrikeRecordDto;
import com.xqboss.apps.enums.Material.MaterialTypeEnum;
import com.xqboss.apps.enums.game.GameCodeEnum;
import com.xqboss.apps.enums.nft.CommonBooleanEnum;
import com.xqboss.apps.enums.user.*;
import com.xqboss.apps.mapper.game.GameMaterialStrikeRecordMapper;
import com.xqboss.apps.service.user.SysUserWalletService;
import com.xqboss.apps.service.user.UserAssetLidouService;
import com.xqboss.apps.service.user.UserAssetWrenchRecordService;
import com.xqboss.apps.service.user.UserAssetWrenchService;
import com.xqboss.apps.vo.game.materialStrike.*;
import com.xqboss.common.annotation.CacheRedis;
import com.xqboss.common.constant.CacheConstants;
import com.xqboss.common.core.domain.R;
import com.xqboss.common.core.domain.entity.SysDictData;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.SecurityUtils;
import com.xqboss.system.service.ISysConfigService;
import com.xqboss.system.service.ISysDictTypeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.xqboss.common.utils.AsyncHandlerUtil.run;


@Slf4j
@Service
public class GameMaterialStrikeRecordService extends ServiceImpl<GameMaterialStrikeRecordMapper, GameMaterialStrikeRecord> {

    @Resource
    private GameService gameService;
    @Resource
    private SysUserWalletService sysUserWalletService;//消耗热豆
    @Resource
    private UserAssetWrenchRecordService userAssetWrenchRecordService;//消耗扳手
    @Resource
    private ISysConfigService sysConfigService;
    @Resource
    private SysUserWalletService userWalletService;
    @Resource
    private UserAssetWrenchService userAssetWrenchService;

    @Resource
    private UserAssetLidouService userAssetLidouService;

    @Resource
    private ISysDictTypeService dictTypeService;

    @Resource
    private RedisCache redisCache;
    @Resource
    private GameMaterialStrikeRecordService gameMaterialStrikeRecordService;

    public static final int MULTI_STATIC_NUM = 1000000000;
    /**
     * 防止人员配置错误，给个风控限制
     */
    public static final BigDecimal MAX_RISK_RATE_LIMIT = BigDecimal.valueOf(0.99);
    private static final Random random = new Random();

    /**
     * 材料冲击首页数据
     *
     * @return
     */
    public GameMaterialStrikeIndexResultVO index(Long userId) {

        //获取热豆余额
        BigDecimal hotBeansBalance = userWalletService.getWallet(userId, UserWalletEnum.INTEGRAL).getAmount().stripTrailingZeros();
        //获取扳手余额
        BigDecimal wrenchBalance = userAssetWrenchService.getWrench(userId).getAmount().stripTrailingZeros();
        //获取力豆余额
        BigDecimal liDouBalance = userAssetLidouService.getLidouInfo(userId).getAmount().setScale(2,RoundingMode.HALF_UP).stripTrailingZeros();
        // 材料冲击游戏配置
        GameMaterialStrikeConfigJson configJson = JSONObject.parseObject(sysConfigService.getValue(Config.Sys.Game.材料冲击游戏配置), GameMaterialStrikeConfigJson.class);
        Assert.notNull(configJson,()->new ServiceException("配置错误~"));
        // 是否开启热豆/扳手材料冲击
        List<SysDictData> data = dictTypeService.selectDictDataByType("game_getABreak_switch");
        Boolean isOpenHotBeans = Boolean.FALSE;
        Boolean isOpenWrench = Boolean.FALSE;
        Boolean isOpenLiDou = Boolean.FALSE;
        if (CollUtil.isNotEmpty(data)) {
            Map<String, String> map = data.stream().collect(Collectors.toMap(SysDictData::getDictValue, SysDictData::getStatus));
            isOpenHotBeans = map.get(MaterialTypeEnum.TYPE_HOTBEANS.getCode())==null?Boolean.FALSE:Objects.equals(map.get(MaterialTypeEnum.TYPE_HOTBEANS.getCode()), "0");
            isOpenWrench = map.get(MaterialTypeEnum.TYPE_WRENCH.getCode())==null?Boolean.FALSE:Objects.equals(map.get(MaterialTypeEnum.TYPE_WRENCH.getCode()), "0");
            isOpenLiDou = map.get(MaterialTypeEnum.TYPE_LIDOU.getCode())==null?Boolean.FALSE:Objects.equals(map.get(MaterialTypeEnum.TYPE_LIDOU.getCode()), "0");
        }
        // 返回
        GameMaterialStrikeIndexResultVO resultVO = GameMaterialStrikeIndexResultVO.builder()
                .ConfigKey("sys.game.notice.getABreak.rule") // 来源：AppProfileVo
                .isOpenHotBeans(isOpenHotBeans)
                .isOpenWrench(isOpenWrench)
                .isOpenLiDou(isOpenLiDou)
                .hotBeansBalance(hotBeansBalance)
                .wrenchBalance(wrenchBalance)
                .liDouBalance(liDouBalance)
                .configJson(configJson)
                .build();

        return resultVO;
    }


    /**
     * 当前用户冲击记录
     *
     * @return
     */
    public IPage<GameMaterialStrikeRecord> records(GameMaterialStrikeRecordDto<GameMaterialStrikeRecord> dto, Long userId) {
        return this.page(
                dto.newPage(),
                Wrappers.lambdaQuery(GameMaterialStrikeRecord.class)
                        .eq(GameMaterialStrikeRecord::getUserId, userId)
                        .eq(GameMaterialStrikeRecord::getMergeType,dto.getMergeType())
                        .orderByDesc(GameMaterialStrikeRecord::getCreateTime)
        );

    }


    /**
     * 材料冲击
     *
     * @param dto
     * @return
     */
    public R<GameMaterialStrikePlayResultVO> play(GameMaterialStrikePlayDto dto,Long userId) {

        if (!gameService.verifyUserId(GameCodeEnum.CODE_B,userId)) {
            throw new ServiceException("游戏内测中~");
        }

        //获取必要数据
        GameMaterialStrikeIndexResultVO index = index(userId);
        GameMaterialStrikeConfigJson configJson = index.getConfigJson();
        if (configJson == null || CollUtil.isEmpty(configJson.getRiskList())) {
            throw new ServiceException("配置错误~");
        }

        //判断材料余额
        if (dto.getMergeType() == MaterialTypeEnum.TYPE_HOTBEANS) {
            // 获取热豆余额
            BigDecimal hotBeansBalance = index.getHotBeansBalance();
            Assert.isTrue(hotBeansBalance.compareTo(BigDecimal.valueOf(dto.getPutNum())) >= 0,"余额不足~");
        }
        if (dto.getMergeType() == MaterialTypeEnum.TYPE_WRENCH) {
            // 获取扳手余额
            BigDecimal wrenchBalance = index.getWrenchBalance();
            Assert.isTrue(wrenchBalance.compareTo(BigDecimal.valueOf(dto.getPutNum())) >= 0,"余额不足~");
        }
        if (dto.getMergeType() == MaterialTypeEnum.TYPE_LIDOU) {
            // 获取力豆余额
            BigDecimal liDouBalance = index.getLiDouBalance();
            Assert.isTrue(liDouBalance.compareTo(BigDecimal.valueOf(dto.getPutNum())) >= 0,"余额不足~");
        }

        // 计算冲击材料范围
        final int maxGetNum = NumberUtil.div(dto.getPutNum(), configJson.getStrikeMinRate()).intValue();
        final int minGetNum = NumberUtil.div(dto.getPutNum(), configJson.getStrikeMaxRate()).intValue();

        // 校验数据
        Assert.isTrue(
                // 使用范围判断
                dto.getPutNum() >= configJson.getPutMinNum() &&
                        dto.getPutNum() <= configJson.getPutMaxNum() &&
                        // 冲击范围判断
                        dto.getTargetNum().compareTo(BigDecimal.valueOf(minGetNum)) >= 0 &&
                        dto.getTargetNum().compareTo(BigDecimal.valueOf(maxGetNum)) <= 0 &&

                        BigDecimal.valueOf(dto.getPutNum()).compareTo(dto.getTargetNum()) < 0,
                "投入数量有误"
        );

        // 获取risk系数
        final double tmpNum = dto.getTargetNum().doubleValue() - dto.getPutNum();
        BigDecimal riskRate = null;
        for (GameMaterialStrikeConfigJson.RiskBean riskBean : configJson.getRiskList()) {
            if (riskBean.getMin() <= tmpNum && tmpNum < riskBean.getMax()) {
                riskRate = riskBean.getRiskRate();
                break;
            }
        }
        if (riskRate == null || riskRate.compareTo(MAX_RISK_RATE_LIMIT) > 0) {
            throw new ServiceException("配置错误");
        }

        // 计算手续费，保留两位小数，直接去掉第三个小数
        final BigDecimal serverCommission = NumberUtil.mul(dto.getTargetNum(), configJson.getCommissionRate())
                .setScale(2, RoundingMode.FLOOR);
        Assert.isTrue(serverCommission.compareTo(dto.getCommission()) == 0, "手续费错误~");

        // 自己调用自己，使事务方法生效
        GameMaterialStrikePlayResultVO resultVO = gameMaterialStrikeRecordService.transactionMethod(dto, serverCommission, riskRate, userId);

        return R.ok(resultVO);
    }

    /**
     * 计算合成结果
     *
     * @param riskRate
     * @param useNum
     * @param getNum
     * @return
     */
    public static boolean isMergeSuccessful(BigDecimal riskRate, Integer useNum, BigDecimal getNum) {
        int successNum = (int) (NumberUtil.div(useNum, getNum).doubleValue() * riskRate.doubleValue() * MULTI_STATIC_NUM);
        int randomVal = random.nextInt(MULTI_STATIC_NUM);
        return randomVal <= successNum;
    }



    /**
     * 事务方法
     *
     * @param dto
     * @param serverCommission
     * @param riskRate
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public GameMaterialStrikePlayResultVO transactionMethod(GameMaterialStrikePlayDto dto,
                                                            BigDecimal serverCommission,
                                                            BigDecimal riskRate,
                                                            Long userId) {
        Long mergeNo = System.currentTimeMillis();

        // 扣减投入（热豆或扳手或力豆）
        if (dto.getMergeType() == MaterialTypeEnum.TYPE_HOTBEANS) {
            // 扣减热豆
            this.sysUserWalletService.reduce(userId, UserWalletEnum.INTEGRAL,BigDecimal.valueOf(dto.getPutNum()), WalletChangeTypeEnum.GAME_MATERIAL_STRIKE_OUT,mergeNo,"时来运转消耗");
        }
        if (dto.getMergeType() == MaterialTypeEnum.TYPE_WRENCH) {
            // 扣减扳手
            this.userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.GAME_MATERIAL_STRIKE_OUT, WalletChangeDirectionEnum.OUT,BigDecimal.valueOf(dto.getPutNum()), mergeNo, "时来运转消耗");
        }
        if (dto.getMergeType() == MaterialTypeEnum.TYPE_LIDOU) {
            // 扣减力豆
            this.userAssetLidouService.updateLidouBalance(userId, WalletLidouChangeTypeEnum.GAME_MATERIAL_STRIKE_OUT, WalletChangeDirectionEnum.OUT,BigDecimal.valueOf(dto.getPutNum()), mergeNo, "时来运转消耗");
        }

        // 计算合成结果
        final boolean mergeResult = isMergeSuccessful(riskRate, dto.getPutNum(), dto.getTargetNum());
        BigDecimal totalAddAmount = BigDecimal.ZERO;
        if (mergeResult) {
            // 合成成功扣除手续费后再进行发放
            totalAddAmount = NumberUtil.sub(dto.getTargetNum(), serverCommission);
            // 发放收入
            if (dto.getMergeType() == MaterialTypeEnum.TYPE_HOTBEANS) {
                // 发放热豆
                this.sysUserWalletService.increase(userId, UserWalletEnum.INTEGRAL,totalAddAmount, WalletChangeTypeEnum.GAME_MATERIAL_STRIKE_IN,mergeNo,"时来运转空投");
            }
            if (dto.getMergeType() == MaterialTypeEnum.TYPE_WRENCH) {
                // 发放扳手
                this.userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.GAME_MATERIAL_STRIKE_IN, WalletChangeDirectionEnum.IN,totalAddAmount, mergeNo, "时来运转空投");
            }
            if (dto.getMergeType() == MaterialTypeEnum.TYPE_LIDOU) {
                // 发放力豆
                this.userAssetLidouService.updateLidouBalance(userId, WalletLidouChangeTypeEnum.GAME_MATERIAL_STRIKE_IN, WalletChangeDirectionEnum.IN,totalAddAmount, mergeNo, "时来运转空投");
            }
        }

        // 材料冲击记录
        GameMaterialStrikeRecord mergeRecord = new GameMaterialStrikeRecord();
        mergeRecord.setRiskRate(riskRate);
        mergeRecord.setUserId(userId);
        mergeRecord.setMergeNo(mergeNo.toString());
        mergeRecord.setMergeStatus(mergeResult ? 1 : 0);
        mergeRecord.setMergeType(dto.getMergeType());
        mergeRecord.setCommission(mergeResult ? serverCommission : BigDecimal.ZERO);
        mergeRecord.setTargetNum(dto.getTargetNum());
        mergeRecord.setPutNum(BigDecimal.valueOf(dto.getPutNum()));
        mergeRecord.setCreateTime(new Date());
        mergeRecord.insert();

        // 处理消息播报
        String nickName = SecurityUtils.getNickName();
        String avatar = SecurityUtils.getAvatar();
        run(()->doMsgBarrage(mergeRecord,nickName,avatar));

        return GameMaterialStrikePlayResultVO.builder()
                .mergeStatus(mergeRecord.getMergeStatus())
                .mergeObtain(totalAddAmount)
                .build();
    }

    /**
     * 弹幕播报
     *
     * @param mergeRecord
     */
    public void doMsgBarrage(GameMaterialStrikeRecord mergeRecord,String nickname,final String avatar) {
        BigDecimal finalAmount = NumberUtil.sub(mergeRecord.getTargetNum(), mergeRecord.getCommission()).setScale(2,RoundingMode.HALF_UP).stripTrailingZeros();
        BigDecimal adminConfig = sysConfigService.getValue(Config.Sys.Game.中奖播报门槛);
        if (adminConfig != null  &&
            finalAmount.compareTo(adminConfig) >= 0 &&
            mergeRecord.getMergeStatus() == CommonBooleanEnum.IS_TRUE.getValue()) {
            GameMaterialStrikeRecordVO mergeRecordVO = BeanUtil.copyProperties(mergeRecord, GameMaterialStrikeRecordVO.class);
            if (nickname.length() >= 2) {
                nickname = nickname.substring(2) + "**";
            } else {
                nickname = nickname.substring(1) + "**";
            }
            mergeRecordVO.setUserName(nickname);
            mergeRecordVO.setAvatarUrl(avatar);
            mergeRecordVO.setTs(System.currentTimeMillis());
            mergeRecordVO.setFinalAmount(finalAmount);
            mergeRecordVO.hideField();
            // mqSender.send(AppMqNormalEnum.WS_MSG_BROADCAST, WsMsgBroadcastEnum.MaterialStrike, mergeRecordVO);
            Long len = redisCache.getListSize(CacheConstants.GAME_MATERIAL_STRIKE_BARRAGE);
            if (len != null && len > 0) {
                redisCache.rightPop(CacheConstants.GAME_MATERIAL_STRIKE_BARRAGE);
            }
            redisCache.listPush(CacheConstants.GAME_MATERIAL_STRIKE_BARRAGE, JSON.toJSONString(mergeRecordVO));
            redisCache.expireList(CacheConstants.GAME_MATERIAL_STRIKE_BARRAGE, RedisKey.REDIS_ONE_DAY_SECONDS, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取需要播报的消息
     *
     * @param ts
     * @return
     */
    public List<com.alibaba.fastjson2.JSONObject> barrage(Long ts) {
        List<com.alibaba.fastjson2.JSONObject> result = redisCache.getCacheList(CacheConstants.GAME_MATERIAL_STRIKE_BARRAGE);
        if (CollUtil.isEmpty(result)) {
            return ListUtil.empty();
        }
        List<com.alibaba.fastjson2.JSONObject> rsNew = Lists.newArrayList();
        for (com.alibaba.fastjson2.JSONObject jo : result) {
            Long ts1 = jo.getLong("ts");
            if (ts == null || ts < ts1) {
                rsNew.add(jo);
            }
        }
        return rsNew;
    }

    /**
     * 投入排行榜
     * @return
     */
    @CacheRedis(expireTime = 10)
    public List<GameRankTopVo> inputTop(MaterialTypeEnum type) {
        return baseMapper.inputTop(type.getValue(), 20);
    }
}
